Skip to content

[CELEBORN-2378] Fix CongestionController never resuming due to Netty pool overhead in getTotalPendingBytes()#3756

Open
Deegue wants to merge 1 commit into
apache:mainfrom
Deegue:CELEBORN-2378
Open

[CELEBORN-2378] Fix CongestionController never resuming due to Netty pool overhead in getTotalPendingBytes()#3756
Deegue wants to merge 1 commit into
apache:mainfrom
Deegue:CELEBORN-2378

Conversation

@Deegue

@Deegue Deegue commented Jul 10, 2026

Copy link
Copy Markdown

What changes were proposed in this pull request?

Use MemoryManager.instance().getPinnedMemory() for low watermark to exit congestion status when using pooled allocator.

Why are the changes needed?

getMemoryUsage() returns nettyUsedDirectMemory + sortMemory, which includes Netty's pooled allocator overhead.

Change to getPinnedMemory() which returns nettyPinnedDirectMemory + sortMemory instead.

As for unpooled allocator, we keep the same behavior as before.

Does this PR resolve a correctness bug?

  • Yes, the worker is supposed to resume when pending data drops below the low watermark.

Does this PR introduce any user-facing change?

No API change.
Workers with congestion control and pooled allocator will now correctly resume from congestion state when actual pending data drops below the low watermark.

How was this patch tested?

UTs

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates worker congestion-control pending-bytes calculation to avoid Netty pooled allocator overhead so the worker can correctly resume once actual pending data drops below the low watermark.

Changes:

  • Switch CongestionController.getTotalPendingBytes() from MemoryManager.getMemoryUsage() to MemoryManager.getPinnedMemory().

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 205 to 207
public long getTotalPendingBytes() {
return MemoryManager.instance().getMemoryUsage();
return MemoryManager.instance().getPinnedMemory();
}
Comment on lines 205 to 207
public long getTotalPendingBytes() {
return MemoryManager.instance().getMemoryUsage();
return MemoryManager.instance().getPinnedMemory();
}
@leixm

leixm commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

It's ok for me, but if we change getMemoryUsage to getPinnedMemory, we should instruct users in migration.md to lower the watermark to avoid off-heap OOM (Out of Memory) errors. Also, we should address the issue of calling getPinnedMemory at 10ms intervals. cc @RexXiong WDYT.

@leixm leixm requested review from RexXiong and leixm July 10, 2026 09:43

@zaynt4606 zaynt4606 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In non-POOLED mode (UNPOOLED or ADAPTIVE), pooledByteBufAllocators stays empty because both fill sites guard with if (allocatorType == POOLED).
So getNettyPinnedDirectMemory() sums an empty list and returns 0, making getTotalPendingBytes() underestimate pending data.

Suggested fix:
fall back to PlatformDependent.usedDirectMemory() when no pooled allocators exist, so non-POOLED configs retain the old behavior.

@Deegue

Deegue commented Jul 14, 2026

Copy link
Copy Markdown
Author

Thanks @zaynt4606 , added a fallback when no pooled allocators exist, we fall back to getMemoryUsage().

@Deegue

Deegue commented Jul 14, 2026

Copy link
Copy Markdown
Author

I also changed the code to keep the enter condition using getTotalPendingBytes() which returns getMemoryUsage() as before. So this fix is only targeting to exit condition with pool allocator.

Gentle ping @leixm @zaynt4606 for further ideas.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 3 comments.

Comment on lines +210 to +217
public long getActivePendingBytes() {
MemoryManager memoryManager = MemoryManager.instance();
long pinnedMemory = memoryManager.getPinnedMemory();
if (pinnedMemory == 0 && NettyUtils.getAllPooledByteBufAllocators().isEmpty()) {
return memoryManager.getMemoryUsage();
}
return pinnedMemory;
}

protected void checkCongestion() {
try {
long pendingConsume = getTotalPendingBytes();
Comment on lines +283 to +285
logger.info(
"Pending consume and produce speed is lower than low watermark, exit congestion control");
}
@Deegue

Deegue commented Jul 14, 2026

Copy link
Copy Markdown
Author

Offline discussed with @leixm , added cache and update interval for getPinnedMemory to avoid rapid call during congestion status.

@SteNicholas

Copy link
Copy Markdown
Member

@Deegue, please discuss this in comments, instead of offline discussion. Otherwise, reviewers and others could not know the input of this change. BTW, please update the title and description according to updates.

@Deegue

Deegue commented Jul 15, 2026

Copy link
Copy Markdown
Author

@Deegue, please discuss this in comments, instead of offline discussion. Otherwise, reviewers and others could not know the input of this change. BTW, please update the title and description according to updates.

Thanks @SteNicholas, PR description updated.

Previously we discussed if it's necessary to add a rate limit and interval to call getPinnedMemory which iterates all pooled Netty allocators. Since it can add noticeable CPU/alloc overhead at this frequency after hitting the high watermark, we decided to add a cache for that.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 3 comments.

this.workerSource = workerSource;
this.sampleTimeWindowSeconds = sampleTimeWindowSeconds;
this.userInactiveTimeMills = conf.workerCongestionControlUserInactiveIntervalMs();
this.activePendingBytesCacheIntervalMs = conf.workerPinnedMemoryCheckIntervalMs();

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The behavior of trimMemoryUsage() is controlled by current workerCongestionControlCheckIntervalMs (default 10ms) which this PR doesn't change.

Pinned memory check interval is designed to follow CelebornConf.WORKER_PINNED_MEMORY_CHECK_INTERVAL according to comments above.

Comment on lines +293 to +295
logger.info(
"Pending consume and produce speed is lower than low watermark, exit congestion control");
}
Comment on lines +288 to +292
if (overHighWatermark.get()) {
long activePendingBytes = getActivePendingBytes();
if (activePendingBytes < workerTrafficQuota.diskBufferLowWatermark()
&& workerProduceSpeed < workerTrafficQuota.workerProduceSpeedLowWatermark()) {
if (overHighWatermark.compareAndSet(true, false)) {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants